In this project, you are going to build a completely standalone, feature-rich digital scoreboard for a badminton (or any racquet) match using an Myduino AIoT Education Kit. It handles score tracking, victory conditions, real-time LED indicators, sound effects, and celebratory lighting.
This exercise teaches you:
- How to use an I2C 16×2 LCD to display real-time game status.
- How to integrate WS2812 (NeoPixel) LED strips for visual effects.
- How to read multiple Push Buttons using
INPUT_PULLUP. - How to use a Buzzer to play custom sounds (
tone()). - How to implement complex game logic (winning conditions, deuce, set points).
Checklist Before Start
- How to connect an I2C LCD to ESP32.
- How to wire and program a WS2812/NeoPixel.
- Understanding digital inputs with
INPUT_PULLUP. - Installing required libraries.
Circuit Connections

| Components | ESP32 Dev Module |
| LCD 16×2 I2C SDA | SDA |
| LCD 16×2 I2C SCL | SCL |
| WS2812 RGB LED | IO15 |
| RED SCORE BUTTON | IO23 |
| BLUE SCORE BUTTON | IO13 |
| RESET BUTTON | IO14 |
| RED LED | IO25 |
| BLUE LED | IO26 |
| BUZZER | IO27 |
Code Lab

Step 1: Install Libraries
Open the Library Manager at the sidebar and install the following library:
- LiquidCrystal I2C (by Frank de Brabander).
- Adafruit Neopixel (by Adafruit).
- Wire.h: (Built-in)

Step 2: Code
Copy and paste the following code into your Arduino IDE:
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Adafruit_NeoPixel.h>
// ✅ Use your confirmed LCD address (change if needed)
LiquidCrystal_I2C lcd(0x27, 16, 2); // 16x2 LCD with I2C address 0x27
// WS2812 LED Strip Configuration
#define LED_PIN 15 // Change to your actual pin
#define LED_COUNT 6 // 6 WS2812 LEDs
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
#define RED_BUTTON 23
#define BLUE_BUTTON 13
#define RESET_BUTTON 14
#define RED_LED 25
#define BLUE_LED 26
#define BUZZER 27
int redScore = 0;
int blueScore = 0;
bool gameOver = false;
void setup() {
Wire.begin(); // Required for ESP32 I2C
lcd.init();
lcd.backlight();
// Initialize WS2812 strip
strip.begin();
strip.show(); // Initialize all pixels to 'off'
strip.setBrightness(50); // Set brightness (0-255)
pinMode(RED_BUTTON, INPUT_PULLUP);
pinMode(BLUE_BUTTON, INPUT_PULLUP);
pinMode(RESET_BUTTON, INPUT_PULLUP);
pinMode(RED_LED, OUTPUT);
pinMode(BLUE_LED, OUTPUT);
pinMode(BUZZER, OUTPUT);
showStartScreen();
}
void loop() {
if (!gameOver) {
if (digitalRead(RED_BUTTON) == LOW) {
delay(50);
redScore++;
playScoreSound();
checkWin();
updateDisplay();
}
if (digitalRead(BLUE_BUTTON) == LOW) {
delay(50);
blueScore++;
playScoreSound();
checkWin();
updateDisplay();
}
}
if (digitalRead(RESET_BUTTON) == LOW) {
delay(300);
resetGame();
}
}
void showStartScreen() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Badminton Match");
lcd.setCursor(0, 1);
lcd.print("Press any button");
// Wait until any button is pressed
while (digitalRead(RED_BUTTON) == HIGH &&
digitalRead(BLUE_BUTTON) == HIGH &&
digitalRead(RESET_BUTTON) == HIGH) {
delay(10); // small delay to reduce CPU use
}
// Optional: clear and pause briefly before showing scores
lcd.clear();
delay(300);
updateDisplay();
}
void updateDisplay() {
lcd.clear();
// Line 0: RED score at column 7
lcd.setCursor(3, 0);
lcd.print("RED ");
lcd.print(redScore);
// Line 1: BLUE score at column 10
lcd.setCursor(7, 1);
lcd.print("BLUE ");
lcd.print(blueScore);
// LED logic
if (redScore > blueScore) {
digitalWrite(RED_LED, HIGH);
digitalWrite(BLUE_LED, LOW);
} else if (blueScore > redScore) {
digitalWrite(RED_LED, LOW);
digitalWrite(BLUE_LED, HIGH);
} else {
digitalWrite(RED_LED, LOW);
digitalWrite(BLUE_LED, LOW);
}
}
void checkWin() {
if ((redScore >= 21 || blueScore >= 21) && abs(redScore - blueScore) >= 2) {
gameOver = true;
announceWinner();
}
if (redScore >= 30 || blueScore >= 30) {
gameOver = true;
announceWinner();
}
}
void announceWinner() {
lcd.clear();
lcd.setCursor(0, 0);
// Centered winner message (on 16x2 LCD)
if (redScore > blueScore) {
lcd.setCursor(1, 0); // RED TEAM WINS!
lcd.print("RED TEAM WINS!");
// Turn on red LED only (no flashing)
digitalWrite(RED_LED, HIGH);
digitalWrite(BLUE_LED, LOW);
// Flash WS2812 LEDs in red, then play music
flashWS2812Victory(true); // Red wins
} else {
lcd.setCursor(0, 0); // BLUE TEAM WINS!
lcd.print("BLUE TEAM WINS!");
// Turn on blue LED only (no flashing)
digitalWrite(RED_LED, LOW);
digitalWrite(BLUE_LED, HIGH);
// Flash WS2812 LEDs in blue, then play music
flashWS2812Victory(false); // Blue wins
}
// Play the triumphant victory fanfare AFTER the LED show
playTriumphantVictoryFanfare();
}
void resetGame() {
redScore = 0;
blueScore = 0;
gameOver = false;
digitalWrite(RED_LED, LOW);
digitalWrite(BLUE_LED, LOW);
// Turn off all WS2812 LEDs
strip.clear();
strip.show();
noTone(BUZZER);
showStartScreen();
}
// 🔊 Enhanced scoring sound
void playScoreSound() {
tone(BUZZER, 600, 80); // Low tone
delay(90);
tone(BUZZER, 800, 80); // Mid tone
delay(90);
tone(BUZZER, 1000, 120); // High tone
delay(150);
}
// 🎆 WS2812 Victory Flash - Flash winner color before music
void flashWS2812Victory(bool redWins) {
uint32_t winnerColor = redWins ? strip.Color(0, 255, 0) : strip.Color(0, 0, 255); // Red or Blue
// Flash WS2812 LEDs 8 times in winner color
for (int i = 0; i < 8; i++) {
// Turn on all WS2812 LEDs in winner color
for (int j = 0; j < LED_COUNT; j++) {
strip.setPixelColor(j, winnerColor);
}
strip.show();
delay(200);
// Turn off all WS2812 LEDs
strip.clear();
strip.show();
delay(200);
}
// Final state - keep WS2812 LEDs on in winner color
for (int j = 0; j < LED_COUNT; j++) {
strip.setPixelColor(j, winnerColor);
}
strip.show();
}
// 🏆 TRIUMPHANT VICTORY FANFARE - Much more victorious!
void playTriumphantVictoryFanfare() {
// Opening fanfare - "Ta-da-da-DAAA!"
tone(BUZZER, 523, 200); delay(220); // C5
tone(BUZZER, 659, 200); delay(220); // E5
tone(BUZZER, 784, 200); delay(220); // G5
tone(BUZZER, 1047, 400); delay(450); // C6 - triumphant!
delay(100);
// Victory ascending sequence
tone(BUZZER, 880, 150); delay(170); // A5
tone(BUZZER, 988, 150); delay(170); // B5
tone(BUZZER, 1047, 150); delay(170); // C6
tone(BUZZER, 1175, 150); delay(170); // D6
tone(BUZZER, 1319, 300); delay(350); // E6 - higher pitch!
delay(150);
// Triumphant finale - "We are the champions" style
tone(BUZZER, 1047, 250); delay(280); // C6
tone(BUZZER, 1175, 250); delay(280); // D6
tone(BUZZER, 1319, 250); delay(280); // E6
tone(BUZZER, 1397, 500); delay(550); // F6 - hold it!
tone(BUZZER, 1568, 600); delay(700); // G6 - VICTORY!
delay(200);
// Final celebratory flourish
tone(BUZZER, 1319, 150); delay(170); // E6
tone(BUZZER, 1568, 150); delay(170); // G6
tone(BUZZER, 1976, 400); delay(500); // B6 - Ultra high victory note!
// Epic ending
tone(BUZZER, 2093, 800); delay(1000); // C7 - THE ULTIMATE VICTORY NOTE!
}
Step 3: Upload and Run
- Select the correct board (ESP32 Dev Module) and Port.
- Click Upload.
How to use the Digital Badminton Scoreboard

Once the code is uploaded and your hardware is fully assembled, using the scoreboard is simple and intuitive, following the rules of the game.
- Power On and Initialization
- Power up: Connect the ESP32 to a power source (USB cable or battery pack).
- Start screen: The LCD will immediately display the following message:
- Badminton Match
- Press any button
- Start game: Press any one of the three physical buttons (Red score, Blue score, or Reset). This clear the screen and initializes the score display too 0-0.
2. Scoring Point
- The two colored buttons are dedicated to adding points for their respective teams.
| Button | Action | Indicator | Sound |
| RED Button | Scores a point for the Red Team | RED LED turns ON if RED takes the lead | Plays a triple-tone score sound |
| BLUE Button | Scores a point for the Blue Team | BLUE LED turns ON if BLUE takes the lead | Plays a triple-tone score sound |
The score displayed on the LCD updates immediately after each point.
3. Understanding the Display and Indicators
The display shows the current score, and the indicator LED’s show which team is currently ahead:
| LED Status | Scoreboard Status | LCD Display Example |
| RED LED ON | Red Team is leading | RED 15 BLUE 12 |
| BLUE LED ON | Blue team is leading | RED 18 BLUE 20 |
| Both LED’s OFF | The score is tied (deuce or 0-0) | RED 10 BLUE 10 |
4. Game Over (Winning the Match)
The scoreboard automatically checks the winning conditions:
- Standard Win: The first team to reach 21 points with a 2-point lead wins the game (e.g., 21-19, 22-20).
- Deciding Point (Set Point): If the score reaches 29-29, the next point scored (reaching 30-29 or 29-30) immediately wins the game.
When a team wins:
- The
gameOverstate is activated, and no more scoring is allowed. - The LCD displays the winner:
RED TEAM WINS!or BLUE TEAM WINS!. - The WS2812 LED Strip flashes in the winner’s color.
- The Buzzer plays the Triumphant Victory Fanfare.
5. Resetting the Scoreboard
To start a new match:
| Button | Action | State |
| RESET Button | Press and hold for ∼300ms. | The scoreboard resets all scores to 0-0, clears all LEDs, stops the buzzer, and returns to the Start Screen |
System Check and Troubleshooting Guide
| Test | Expected Result | Problem | Solution |
| Start Screen | LCD displays “Badminton Match” and “Press any button” | LCD is blank or garbled | Verify I2C wiring (SDA to 21, SCL to 22) |
| Scoring | Pressing a score button increases score and plays sound | Buttons don’t score | Check wiring: Buttons must connect to the correct GPIO pin |
| LED Indicators | When scores are 5-3, the leading team’s LED (25 or 26) is ON | Indicator LEDs don’t work | Check wiring (GPIO25/26) and resistor connections |
| WS2812(RGB) | Flashes during win fanfare | WS2812 LEDs don’t light up | Check the LED_PIN (GPIO15) |
| Buzzer | Plays sound on scoring and winning | Buzzer does not make sound | Check the buzzer wiring on GPIO27 |
Buy from:
Myduino AIoT Education Kit from myduino.com






